package cn.walls1717.wallsmall.auth.controller;

import cn.hutool.core.util.StrUtil;
import cn.walls1717.wallsmall.auth.feign.MemberFeignService;
import cn.walls1717.wallsmall.auth.feign.ThirdPartyFeignService;
import cn.walls1717.wallsmall.auth.vo.UserLoginVO;
import cn.walls1717.wallsmall.auth.vo.UserRegisterVO;
import cn.walls1717.wallsmall.common.utils.R;
import cn.walls1717.wallsmall.common.pojo.vo.MemberRespVO;
import com.alibaba.fastjson.TypeReference;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import javax.annotation.Resource;
import javax.servlet.http.HttpSession;
import javax.validation.Valid;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static cn.walls1717.wallsmall.common.constant.auth.AuthServerConstant.LOGIN_USER;
import static cn.walls1717.wallsmall.common.constant.auth.AuthServerConstant.SMS_CODE_CACHE_PREFIX;
import static cn.walls1717.wallsmall.common.exception.BizCodeEnum.SMS_CODE_EXCEPTION;

/**
 * 前台登录
 *
 * @author walls1717
 * @since 2022/11/11 15:11
 **/
@Slf4j
@Controller
public class LoginController {
    @Resource
    private ThirdPartyFeignService thirdPartyFeignService;
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Resource
    private MemberFeignService memberFeignService;

    /** 发送验证码 */
    @GetMapping("/sms/code")
    @ResponseBody
    public R sendCode(@RequestParam("phone") String phone) {
        String redisCode = stringRedisTemplate.opsForValue().get(SMS_CODE_CACHE_PREFIX + phone);
        String code = UUID.randomUUID().toString().substring(0, 5);
        // 先判断Redis中是否有数据，没有数据发送短信，并将验证码与时间信息保存到Redis中。
        if (StrUtil.isEmpty(redisCode)) {
            // 验证码与当前时间戳
            String codeTime = code + "_" + System.currentTimeMillis();
            stringRedisTemplate.opsForValue().set(SMS_CODE_CACHE_PREFIX + phone, codeTime, 10, TimeUnit.MINUTES);
            // 发送短信
            thirdPartyFeignService.sendCode(phone, code);
            return R.ok();
        }

        // Redis中有数据，说明已经发送过验证码，判断验证码发送时间是否超过60s，如果没有超过则返回错误。
        long time = Long.parseLong(redisCode.split("_")[1]);
        if (System.currentTimeMillis() - time < 60000) {
            return R.error(SMS_CODE_EXCEPTION.getCode(), SMS_CODE_EXCEPTION.getMsg());
        }

        // 验证码发送时间超过60s，重新发送验证码并覆盖Redis中的数据。
        String codeTime = code + "_" + System.currentTimeMillis();
        stringRedisTemplate.opsForValue().set(SMS_CODE_CACHE_PREFIX + phone, codeTime, 10, TimeUnit.MINUTES);
        thirdPartyFeignService.sendCode(phone, code);
        return R.ok();
    }

    /** 注册 */
    @PostMapping("/register")
    public String register(@Valid UserRegisterVO registerVO, BindingResult result, RedirectAttributes attributes) {
        // 校验数据绑定，如果发现有错误，则讲错误信息返回前端。
        if (result.hasErrors()) {
            /*
              为什么加(v1, v2) -> v1？
              因为对参数设置了两层校验，一层非空一层格式，
              如果两层校验都不满足，那么在封装map的时候就会出现key重复的错误，
              加上了(v1, v2) -> v1  v1表示第一个value，v2表示第二个value，这里使用的箭头函数，也就是说当出现了重复key的数据时，
              会回调这个方法，可以在这个方法里处理重复Key数据问题，这里直接使用了上一个数据。
             */
            Map<String, String> errors =
                    result.getFieldErrors().stream().collect(Collectors.toMap(FieldError::getField, error -> {
                        String defaultMessage = error.getDefaultMessage();
                        assert defaultMessage != null;
                        return defaultMessage;
                    }, (v1, v2) -> v1));
            // 这里是依赖session实现数据共享。
            attributes.addFlashAttribute("errors", errors);
            return "redirect:http://auth.wallsmall.com/reg.html";
        }

        String code = registerVO.getCode();
        String redisCodeTime = stringRedisTemplate.opsForValue().get(SMS_CODE_CACHE_PREFIX + registerVO.getPhone());
        // Redis中没有验证码，重新跳转到注册页。
        if (StrUtil.isEmpty(redisCodeTime)) {
            HashMap<String, String> errors = new HashMap<>(1);
            errors.put("code", "验证码失效，请重新发送");
            attributes.addFlashAttribute("errors", errors);
            return "redirect:http://auth.wallsmall.com/reg.html";
        }

        // Redis中有验证码，对比是否与用户输入验证码一致，如果不一致返回错误信息。
        String redisCode = redisCodeTime.split("_")[0];
        if (!redisCode.equals(code)) {
            HashMap<String, String> errors = new HashMap<>(1);
            errors.put("code", "验证码错误");
            attributes.addFlashAttribute("errors", errors);
            return "redirect:http://auth.wallsmall.com/reg.html";
        }

        // 验证码验证通过，先删除Redis中验证码数据。
        stringRedisTemplate.delete(SMS_CODE_CACHE_PREFIX + registerVO.getPhone());
        // 再进行注册。
        R r = memberFeignService.register(registerVO);
        if (r.getCode() != 0) {
            HashMap<String, String> errors = new HashMap<>(1);
            errors.put("msg", r.get("msg").toString());
            attributes.addFlashAttribute("errors", errors);
            return "redirect:http://auth.wallsmall.com/reg.html";
        }

        return "redirect:http://auth.wallsmall.com/login.html";
    }

    /** 登录 */
    @PostMapping("/login")
    public String login(UserLoginVO loginVO, RedirectAttributes attributes, HttpSession session) {
        R r = memberFeignService.login(loginVO);
        if (r.getCode() != 0) {
            HashMap<String, String> errors = new HashMap<>();
            errors.put("msg", r.get("msg").toString());
            attributes.addFlashAttribute("errors", errors);
            return "redirect:http://auth.wallsmall.com/login.html";
        }
        MemberRespVO data = r.getData("data", new TypeReference<MemberRespVO>(){});
        session.setAttribute(LOGIN_USER, data);
        return "redirect:http://wallsmall.com";
    }

    /** 回到首页 */
    @GetMapping("/login.html")
    public String loginPage(HttpSession session) {
        Object attribute = session.getAttribute(LOGIN_USER);
        if (attribute != null) {
            return "redirect:http://wallsmall.com";
        }
        return "login";
    }
}
